ICTSC2020 k8s運用解説 後編:運用編
こんにちは。@takemioIOです。
この記事は ICTSC2020 k8s運用解説、前編:構築と構成の後編 にあたります。
ここでは以前の記事ではk8sを構築したことについてと全体像について述べました。ここではその構築したk8sをどのように利用したのか、それらを通じた知見を述べます。
CDについて
こんにちは。CI/CDに関する部分を担当した sakuraiです
今年はArgoCDとHelmfileを利用してクラスタごとにスコアサーバー(コンテストサイト)などをデプロイする運用をしました。
背景
クラスタチームではKubernetesクラスタを3つ運用し、次のような位置づけとしています。
- wspクラスタ(監視系、ダッシュボードなど)
- devクラスタ(開発用テスト環境)
- prdクラスタ(コンテスト参加者へ提供する本番環境)
各クラスタへのアプリケーションのデプロイを行うためには、そのクラスタに対してkubectl apply -f hogehoge.yaml
といったコマンドを打つ必要があります。しかし、これを手作業で行うことは
- 単純に手間
- 人為的なミスが起こりうる
- アプリケーション側の人間がクラスタを触る必要がある
ということがあります。そこで、クラスタへのデプロイを自動化したりクラスタチーム以外がk8s上にアプリケーションをデプロイするときの動線を整備したりすることによって、k8sとその上のアプリケーションの運用を継続的に行えることを目指しました。
Helmfile
まず初めに、これまでスクリプトでごり押されていたマニフェスト群をテンプレート化を行いました。テンプレート化にはKustomizeとHelmが候補となりましたが、環境変数の変更のしやすさの観点からHelmを使用することにしました。また、要件としてアプリケーションを各クラスタへデプロイする際、環境変数を変えることでConfigMapやimageを変更できる必要がありました。このテンプレート化では例えば、
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: {{ .Values.__VAR__NAMESPACE }}
...
values.yaml
__VAR__NAMESPACE: "scoreserver"
というようにマニフェストの一部を変数として、その変数に対応する内容を記したファイルを用意することで目的のマニフェストを出力することができます。
# helm template --values values.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: scoreserver
...
さらに、この変数の内容(values.yaml)を各クラスタと紐づけるためにHelmfileを利用しました。HelmfileではHelmのテンプレートに加えて環境を定義することができます。ここでdevelop/productionというように環境を定義し、環境変数も紐づけていきます。
helmfile.yaml
environments:
workspace:
values:
- environment/workspace/values.yaml
develop:
values:
- environment/develop/values.yaml
production:
values:
- environment/production/values.yaml
...
単純な置き換えとしては次のようになります。
helm template --values environment/production/values.yaml
↓
helmfile --environment production template .
Argo CD
クラスタへのアプリケーションデプロイの自動化ツールとして、Argo CDを利用しました。Argo CDはGitHub上のマニフェストを参照して、アプリケーションのデプロイを行うことができます。また、GitHub上でそのマニフェストが変更された場合に自動でデプロイを行うことができます。したがって、Argo CD上でデプロイ設定を行った後はGitHub上でマニフェストを管理することでデプロイを行うことができるため、アプリケーション管理者がクラスタへ触る必要がなくなります。
(Argo CD自体の導入はとても簡単なので内容としては割愛します。GUIで設定できるし適当に使いたいときもおすすめできそう。)
Argo CDはhelmなどのテンプレートエンジンに対応していますが、Helmfileには対応してないためプラグインとして追加します。
deploy.yaml
spec:
template:
spec:
# 1. Define an emptyDir volume which will hold the custom binaries
volumes:
- name: custom-tools
emptyDir: {}
# 2. Use an init container to download/copy custom binaries into the emptyDir
initContainers:
- name: download-tools
image: alpine:3.8
command: [sh, -c]
args:
- wget -qO /custom-tools/helmfile https://github.com/roboll/helmfile/releases/download/v0.128.0/helmfile_linux_amd64 && chmod +x /custom-tools/*
volumeMounts:
- mountPath: /custom-tools
name: custom-tools
containers:
- name: argocd-repo-server
volumeMounts:
- mountPath: /usr/local/bin/helmfile
name: custom-tools
subPath: helmfile
kubectl -n argocd patch deploy/argocd-repo-server -p "$(cat deploy.yaml)"
アプリケーション情報の登録はCUIまたはGUIで行うことができるが、こちらもテキストで管理が可能です。
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: scoreserver-production
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: production
source:
repoURL: /* CENSORED */
path: scoreserver
targetRevision: master
plugin:
name: helmfile
env:
- name: ENV
value: production
destination:
name: kubernetes-prd #ここでクラスタを指定する。Argo CDが動いているクラスタ以外は別途事前登録する必要がある。
namespace: scoreserver-production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Argo CDにはadminユーザがデフォルトで設定されていますが、ユーザの追加を行ったり権限設定を行うことが可能です。(設定方法)
ICTSCではprdクラスタへの変更を制限して、本番環境への破壊や意図しない変更を防ぐようにしています。
Argo CD notifications
デプロイ結果をSlackに通知するためにArgo CD notificationsを使いました。
(比較的最近v1.0~になって変更が辛かったデスネ)
ドキュメントがアレなんですが、最低限以下だけすれば動きそうです。(別途Slack側でOauth tokenの発行は必要)
# kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/v1.0.2/manifests/install.yaml
# kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/v1.0.2/catalog/install.yaml
# kubectl edit cm -n argocd argocd-notifications-cm
data:
defaultTriggers: |
- on-deployed
- on-health-degraded
- on-sync-failed
- on-sync-running
- on-sync-status-unknown
- on-sync-succeeded
# kubectl edit secret -n argocd argocd-notifications-secret
stringData:
slack-token: CENSORED
以下のような通知が届くようになります。
監視基盤
こんにちは、監視基盤を担当した梅田@x86takaです。
今回は監視基盤として行ったことを説明します。
まず初めに、今年の監視基盤の構成を説明します。
構成
まず、最初に監視対象です。
監視対象はNTT中央研修センタをメインに行いました。
- NTT中央研修センタ
- サーバ
- S7 * 2
- m4 * 2
- RH1288 * 4
- ネットワーク機器
- MX5
- SRX1500
- SN2410
- サーバ
- さくらクラウド
- k8sクラスタ
- prd クラスタ
- wsp クラスタ
- k8sクラスタ
NTT中央研修センタとさくらクラウドの構成は、前回の記事に詳細にかかれています。
また、これらの監視に利用したコンポーネントは以下の通りです。
- 分析
- Elasticsearch
- データ収集
- Logstash
- Prometheus
- Elastiflow
- Zabbix
- 可視化
- Kibana
- Grafana
- その他
- AlertManager
具体的にこれらのコンポーネントを、どのように利用したのかという話をしたいと思います。
Hardware監視
ここでは、サーバのIPMIから得られるデータやネットワーク機器(SNMP)のデータの監視についてです。
今回は、Zabbixを利用しNTT中央研修センタにある合計11台のホストを監視しました。
使用したテンプレートも含め、以下のスクリーンショットを載せておきます。
IPMIからは、サーバのハードウェアの状態の監視を行いました。
IPMIのデータ取得は、Zabbixのデフォルトの設定では行えません。
ENVにZBX_IPMIPOLLERS=1
のように、0以上の値を設定することにより取得できるようになります。
- name: ZBX_IPMIPOLLERS
value: "1"
今回は準備期間中に、HDDの故障が発生したりしていましたので重要な監視になりました。
(Problemのメッセージ)
hardDisk [1] status major
また、ネットワーク機器はSNMPによる監視, メトリクスの取得を行いました。
後述する、GrafanaでZabbixで取得した、対外トラフィックの可視化を行いました。
サービス監視
主にPrometheusを利用し、Grafanaで可視化を行いました。
Prometheus
Prometheusでは Node-expoter, Ceph-expoter, BlackBox-expoterなどを利用しデータの収集を行いました。
Prometheusのデータの永続化についてです。
監視項目が多い場合かなりのストレージを使うため、注意が必要でした。
ICTSC2020の場合、10日間で20GBのストレージを消費しました。
また、PrometheusのDBのサイズが肥大化し起動に時間がかかるようになっていました。
長期の運用を考える場合に考え直さなければならない部分だと思っています。
Deploy
ICTSC2020ではArgoCDによるデプロイを行っています。
しかし、ConfigMapに書かれている設定の変更時にPodの再起動が行われません。
そのため、DeployのアノテーションにConfigMapのhash値をいれておき、ConfigMapに変更があったときのみPodのUpdateが行われるようにしました。
このような形です。
annotations:
checksum/config_volume: {{ $.Files.Get "templates/prometheus-configmap.yaml"| sha256sum }}
checksum/blackbox_volume: {{ $.Files.Get "templates/blackbox_exporter-configmap.yaml"| sha256sum }}
checksum/ceph_target_volume: {{ $.Files.Get "templates/ceph-exporter-target.yaml"| sha256sum }}
checksum/node_volume: {{ $.Files.Get "templates/node_exporter-configmap.yaml"| sha256sum }}
Grafana
Grafanaでは、DatasourceにPrometheus, Zabbixを利用し可視化していました。
全体で3つGrafanaが存在したため、Dashboardのjsonをk8sのConfigMapで管理しています。
改善点
ICTSCではConfigMapの定義yamlに、すべてのdashboardのjsonが以下のように一つのファイルに書かれています。
---
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: null
name: grafana-import-dashboards
namespace: monitoring
data:
grafana-net-2-dashboard.json: |
{
"__inputs": [{
"name": "DS_PROMETHEUS",
"label": "Prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}],
"__requires": [{
"type": "panel",
"id": "singlestat",
"name": "Singlestat",
"version": ""
}, {
"type": "panel",
"id": "text",
"name": "Text",
"version": ""
}, {
.........
しかし、この状態だとdashboardの変更した際にConfigMapの定義ファイルとjsonが同じところにあるため書き換えが大変でした。
そのため、jsonとConfigMapの定義を分離することにしました。
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: null
name: grafana-import-dashboards
namespace: monitoring
data:
grafana-net-2-dashboard.json: |-
{{ .Files.Get "dashboard/grafana-net-2-dashboard.json" | indent 4}}
templateに置かれているConfigMap定義から、別ディレクトリに置かれているjsonを読み込む形に変更しました。
その際にindent 4
をつけることによって、yamlに読み込まれた際のインデントを気にする必要がなくなります。
k8s-dashboard.json: |-
{{ .Files.Get "dashboard/k8s-dashboard.json" | indent 4}}
また、サーバの監視にGrafana.comに公開されているdashboardを利用しました。
https://grafana.com/grafana/dashboards/11074
上にある公開されているDashboardの一部において、jsonの定義でConfigMapのサイズ制限を超えてしまう問題が発生しました。
GrafanaのAPIを利用して、Grafana.comから取得したデータのUploadを行い回避を行いました。
grafana_host="http://grafana:3000";
grafana_cred="${GF_ADMIN_USER}:${GF_ADMIN_PASSWORD}";
grafana_datasource="prometheus";
ds=(2842 1860 11074);
for d in "${ds[@]}"; do
echo -n "Processing $d: ";
j=$(curl -s -k -u "$grafana_cred" $grafana_host/api/gnet/dashboards/"$d" | jq .json);
echo "{\"dashboard\":$j,\"overwrite\":true, \
\"inputs\":[{\"name\":\"DS_PROMETHEUS\",\"type\":\"datasource\", \
\"pluginId\":\"prometheus\",\"value\":\"$grafana_datasource\"}]}" \
| curl -s -k -u "$grafana_cred" -XPOST -H "Accept: application/json" \
-H "Content-Type: application/json" \
$grafana_host/api/dashboards/import -d "@-" ;
echo "" ;
done
今回は行いませんでしたが、Grafana.comのようなPrivateなDashbordの公開場所を設けるなど、
k8sのConfigMapに書き込まない方がよいと感じました。
また、以前公開しているPomeriumの記事に関連してGrafanaの認証をGitHubで行えるようにしています。
以下のような設定をgrafana.iniに書くことによってGitHub OAuth2を有効にしました
[auth.github]
enabled = true
allow_sign_up = true
client_id = {{ .Values.github_clientid }}
client_secret = {{ .Values.github_client_secret }}
scopes = user:email,read:org
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
team_ids = xxxxxxx
allowed_organizations = ictsc
GitHub Organizationで利用している場合、team_idsという設定項目でGitHub Organizationの特定のTeamのみ利用できるといった指定をすることができます。
team_idsというものは、チーム名ではなくGithubAPIから取得できる数字のIDを書かなければなりません。
GitHubのwebからは取得できないため、curlで取得する必要があります。
GrafanaのSession管理
また、Grafanaはセッション管理などにSQLiteを利用しています。
Grafanaをレプリカを行って運用する場合にはDBのLockがかかってしまうことがあります。
具体的に、DBのLockがかかるとGrafanaから突然ログアウトされるような現象が発生します。
そのため、MySQLサーバなどを用意しgrafana.iniで以下のようにデータベース接続の設定を行うことで解消します。
databaseに接続情報を設定し、[session]
をmysql変更します。
[database]
# You can configure the database connection by specifying type, host, name, user and password
# as seperate properties or as on string using the url propertie.
# Either "mysql", "postgres" or "sqlite3", it's your choice
type = mysql
host = helm-grafana-mysql:3306
name = grafana
user = ictsc
# If the password contains # or ; you have to wrap it with trippel quotes. Ex """#password;"""
password = hogehoge
[session]
# Either "memory", "file", "redis", "mysql", "postgres", default is "file"
provider = mysql
ネットワーク監視
次は、ネットワーク監視についてです。
主に、DataSourceはSNMP, sflowからデータを取得を行いました。
監視対象は、NTT中央研修センターにあるネットワーク機器です。
繰り返しになりますが、列挙しておきます。
- ネットワーク機器
- MX5
- SRX1500
- SN2410
これら3台の機器を、ZabbixからSNMPでデータ取得を行いました。
これらの機器のICTSC2020での使用用途を簡単に説明します。
- MX5
HomeNOC様とのBGPフルルートを受け取っているルータです。
2拠点と接続し冗長化を行っています。 - SRX1500
MX5の配下に接続されている、Firewallです。 - SN2410
サーバ間の通信など、データ通信のコアスイッチとして利用しています。
これらの機器から取得したデータの可視化を行いました。
まず、HomeNOC様との対外トラフィックの可視化についてです。
以下は、取得したデータをZabbixのScreenで表示させたのものです。
今回はGrafanaで様々な監視のdashboardを扱っていますので、GrafanaでZabbixのデータを表示を行います。
GrafanaからZabbixのデータを表示するために、以下のPluginのインストールを行います。
https://grafana.com/grafana/plugins/alexanderzobnin-zabbix-app/
この際、zipファイルからインストール作業をする必要はなく、k8sのマニフェストからENVでインストールするプラグインを指定できます。
- name: GF_INSTALL_PLUGINS
value: "alexanderzobnin-zabbix-app"
今回、ZabbixをDatasourceとしたDashboardとして対外トラフィックの可視化を行いました。
以下は、本戦二日間の実際のDashboardになります。
Elastiflow
Elastiflowを利用したflow情報の可視化です。
構築はElastiflowのdocker-composeファイルを参考にk8sのtemplateを書いて構築をしました。
https://github.com/robcowart/elastiflow/blob/master/docker-compose.yml
参考までに、作成したelastiflow-logstash用のファイルを載せておきます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: elastiflow
namespace: monitoring
spec:
selector:
matchLabels:
app: elastiflow
replicas: 2
template:
metadata:
labels:
app: elastiflow
spec:
containers:
- name: elastiflow
image: robcowart/elastiflow-logstash:4.0.1
env:
- name: LS_JAVA_OPTS
value: "-Xms3g -Xmx3g"
- name: ELASTIFLOW_AGENT_ID
value: "elastiflow"
- name: ELASTIFLOW_GEOIP_CACHE_SIZE
value: "16384"
- name: ELASTIFLOW_GEOIP_LOOKUP
value: "true"
- name: ELASTIFLOW_ASN_LOOKUP
value: "true"
- name: ELASTIFLOW_OUI_LOOKUP
value: "true"
- name: ELASTIFLOW_POPULATE_LOGS
value: "true"
- name: ELASTIFLOW_KEEP_ORIG_DATA
value: "true"
- name: ELASTIFLOW_DEFAULT_APPID_SRCTYPE
value: "__UNKNOWN"
- name: ELASTIFLOW_RESOLVE_IP2HOST
value: "true"
- name: ELASTIFLOW_NAMESERVER
value: "127.0.0.1"
- name: ELASTIFLOW_DNS_HIT_CACHE_SIZE
value: "25000"
- name: ELASTIFLOW_DNS_HIT_CACHE_TTL
value: "900"
- name: ELASTIFLOW_DNS_FAILED_CACHE_SIZE
value: "75000"
- name: ELASTIFLOW_DNS_FAILED_CACHE_TTL
value: "3600"
- name: ELASTIFLOW_ES_HOST
value: "elasticsearch:9200"
- name: ELASTIFLOW_NETFLOW_IPV4_PORT
value: "2055"
- name: ELASTIFLOW_NETFLOW_UDP_WORKERS
value: "4"
- name: ELASTIFLOW_NETFLOW_UDP_QUEUE_SIZE
value: "4096"
- name: ELASTIFLOW_NETFLOW_UDP_RCV_BUFF
value: "33554432"
- name: ELASTIFLOW_SFLOW_IPV4_PORT
value: "6343"
- name: ELASTIFLOW_SFLOW_UDP_WORKERS
value: "4"
- name: ELASTIFLOW_SFLOW_UDP_QUEUE_SIZE
value: "4096"
- name: ELASTIFLOW_SFLOW_UDP_RCV_BUFF
value: "33554432"
- name: ELASTIFLOW_IPFIX_UDP_IPV4_PORT
value: "4739"
- name: ELASTIFLOW_IPFIX_UDP_WORKERS
value: "2"
- name: ELASTIFLOW_IPFIX_UDP_QUEUE_SIZE
value: "4096"
- name: ELASTIFLOW_IPFIX_UDP_RCV_BUFF
value: "33554432"
---
apiVersion: v1
kind: Service
metadata:
name: elastiflow
namespace: monitoring
annotations:
metallb.universe.tf/address-pool: privateIP
spec:
ports:
- name: netflow-port
port: 2055
protocol: UDP
targetPort: 2055
- name: sflow-port
port: 6343
protocol: UDP
targetPort: 6343
- name: ipfix-port
port: 4739
protocol: UDP
targetPort: 4739
selector:
app: elastiflow
type: LoadBalancer
ElastiflowのDashboardは以下にあるjsonファイルをKibanaからimportを行うことによって、みれるようになります。
https://github.com/robcowart/elastiflow/tree/master/kibana
コンテスト中のflow数はこのような形です。
監視基盤については以上です。
終わりに
今回k8sをどのように利活用したかについて説明しました。これが今回の我々の成果になります!
前編・後編と説明しましたが面白く読んでもらえましたでしょうか?
最後になりますが参加してくださった皆さん、スポンサーとしてリソース提供をしてくださったさくらインターネット様ありがとうございました。
もし今回の記事が皆さんがk8sを運用するにあたっての参考になれば幸いです。